using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Unity.Mathematics;
using System.Threading;
using Geometry;
using EarthGeospatial;
using Cysharp.Threading.Tasks;
using Gltf;

namespace Cesium3DTilesSelection
{
    public class Tile
    {
        public TileContext pContext { get; set; }

        public Tile pParent { get; set; }

        public List<Tile> children { get; set; }
        /**
         * The current state of this tile in the loading process.
         */
        public enum LoadState
        {
            /**
             * @brief This tile is in the process of being destroyed.
             *
             * Any pointers to it will soon be invalid.
             */
            Destroying = -3,

            /**
             * @brief Something went wrong while loading this tile and it will not be
             * retried.
             */
            Failed = -2,

            /**
             * @brief Something went wrong while loading this tile, but it may be a
             * temporary problem.
             */
            FailedTemporarily = -1,

            /**
             * @brief The tile is not yet loaded at all, beyond the metadata in
             * tileset.json.
             */
            Unloaded = 0,

            /**
             * @brief The tile content is currently being loaded.
             *
             * Note that while a tile is in this state, its {@link Tile::getContent},
             * and {@link Tile::getState}, methods may be called from the load thread,
             * and the state may change due to the internal loading process.
             */
            ContentLoading = 1,

            /**
             * @brief The tile content has finished loading.
             */
            ContentLoaded = 2,

            /**
             * @brief The tile is completely done loading.
             */
            Done = 3
        }
        // Properties from tileset.json.
        // These are immutable after the tile leaves TileState::Unloaded.
        public BoundingVolume boundingVolume { get; set; }
        public BoundingVolume viewerRequestVolume { get; set; }
        public double geometricError { get; set; }
        public TileRefine refine { get; set; }
        public double4x4 transform { get; set; }

        public TileID _id = new TileID();
        public TileID ID { get { return _id; } private set { _id = value; } }
        public BoundingVolume contentBoundingVolume { get; set; }

        // Load state and data.

        private int _state;
        public LoadState state { get { return (LoadState)_state; } set { _state = (int)value; } }
        public TileContentLoadResult pContent { get; set; }

        //    void* _pRendererResources;

        // Selection state
        public TileSelectionState lastSelectionState = new TileSelectionState();

        // Overlays
        public List<RasterMappedTo3DTile> rasterTiles = new List<RasterMappedTo3DTile>();

        public LinkedList<Tile> _loadedTilesLinks;

        public Int64 computeByteSize()
        {
            return 0;
        }

        public Tileset pTileset => pContext.pTileset;
        public object pRendererResources { get; private set; }
        public static implicit operator bool(Tile self)
        {
            return self == null ? false : true;
        }

        public Tile()
        {
            pContext = null;
            children = new List<Tile>(4);
            transform = double4x4.identity;
        }

        public void createChildTiles(int count)
        {
            if (this.children.Any())
            {
                Debug.Log("Children already created.");
            }
            for (var i = 0; i < count; i++)
            {
                this.children.Add(new Tile());
            }
        }

        public void createChildTiles(List<Tile> children)
        {
            if (this.children.Any())
            {
                Debug.Log("Children already created.");
            }
            this.children = children;
        }

        public void update(int lastFrameNumber, int currentFrameNumber)
        {
            TilesetExternals externals = pContext.pTileset.externals;
            if (this.state == LoadState.FailedTemporarily)
            {
                if (this.pContext.failedTileCallback != null)
                {
                    var action = this.pContext.failedTileCallback.Invoke(this);
                    switch (action)
                    {
                        case FailedTileAction.GiveUp:
                            this.setState(LoadState.Failed);
                            break;
                        case FailedTileAction.Retry:
                            this.unloadContent();
                            break;
                        case FailedTileAction.Wait:
                            break;
                    }
                }
                else
                {
                    this.setState(LoadState.Failed);
                }
            }
            if (state == LoadState.ContentLoaded)
            {
                if (externals.pPrepareRendererResources != null)
                {
                    this.pRendererResources = externals.pPrepareRendererResources.prepareInMainThread(this, this.pRendererResources);
                }
                if (this.pContent != null)
                {
                    if (this.pContent.childTiles != null && this.children.Any())
                    {
                        foreach (var childTile in this.children)
                        {
                            childTile.pParent = this;
                        }

                        this.createChildTiles(pContent.childTiles);
                        if (this.pContent.pNewTileContext != null)
                        {
                            this.pContent.pNewTileContext.ContextInitializerCallback?.Invoke(this.pContext, this.pContent.pNewTileContext);
                        }
                        this.pTileset.addContext(pContent.pNewTileContext);
                    }
                    if (this.pContent.model == null)
                    {
                        this.setUnconditionallyRefine();
                    }
                    if (pContent.updatedBoundingVolume != null)
                    {
                        this.boundingVolume = pContent.updatedBoundingVolume;
                    }
                    if (this.pContent.availableTileRectangles.Any() && pContext.implicitContext != null)
                    {
                        var context = pContext.implicitContext;
                        foreach (var range in pContent.availableTileRectangles)
                        {
                            context.availability.addAvailableTileRange(range);
                        }
                    }
                }
                this.setState(LoadState.Done);
            }

            if (this.pContext.implicitContext != null && !this.children.Any() && this._id.quadtreeID != null)
            {
                var implicitContext = pContext.implicitContext;
                var availability = implicitContext.availability;
                var id = this._id.quadtreeID.Value;
                var swID = new QuadtreeTileID(id.level + 1, id.x * 2, id.y * 2);
                var sw = availability.isTileAvailable(swID) ? 1 : 0;

                var seID = new QuadtreeTileID(swID.level, swID.x + 1, swID.y);
                var se = availability.isTileAvailable(seID) ? 1 : 0;

                var nwID = new QuadtreeTileID(swID.level, swID.x, swID.y + 1);
                var nw = availability.isTileAvailable(nwID) ? 1 : 0;

                var neID = new QuadtreeTileID(swID.level, swID.x + 1, swID.y + 1);
                var ne = availability.isTileAvailable(neID) ? 1 : 0;

                int childCount = sw + se + nw + ne;
                if (childCount > 0)
                {
                    this.children.Clear();
                    this.children.Capacity = 4;
                    createImplicitTile(implicitContext, this, this.children[0], swID, sw);
                    createImplicitTile(implicitContext, this, this.children[1], seID, se);
                    createImplicitTile(implicitContext, this, this.children[2], nwID, nw);
                    createImplicitTile(implicitContext, this, this.children[3], neID, ne);
                }
            }

            GlobeRectangle pRectangle = Impl.obtainGlobeRectangle(this.boundingVolume);
            if (!pRectangle)
            {
                return;
            }

            if (this.state == LoadState.Done && this.pTileset.SupportsRasterOverlays && pRectangle)
            {
                bool moreRasterDetailAvailable = false;
                for (var i = 0; i < this.rasterTiles.Count; i++)
                {
                    RasterMappedTo3DTile mappedRasterTile = this.rasterTiles[i];
                    RasterOverlayTile pLoadingTile = mappedRasterTile.pLoadingTile;
                    if (pLoadingTile && pLoadingTile.State == RasterOverlayTile.LoadState.Placeholder)
                    {
                        RasterOverlayTileProvider pProvider = pLoadingTile.pOverlay.getTileProvider();
                        if (pProvider && !pProvider.isPlaceholder() && pContent)
                        {
                            var projections = pContent.rasterOverlayProjections;
                            var it = projections.FindIndex(x => x == pProvider.pProjection);
                            if (it < 0)
                            {
                                this.unloadContent();
                                return;
                            }
                            this.rasterTiles.RemoveAt(i);
                            // i--;

                            Rectangle projectedRectangle = Projection.projectRectangleSimple(pProvider.pProjection, pRectangle);
                            var pTile = pProvider.getTile(projectedRectangle, this.geometricError);


                            RasterMappedTo3DTile mapped = new RasterMappedTo3DTile(pTile);
                            this.rasterTiles.Add(mapped);
                            mapped.setTextureCoordinateID(it);

                        }
                        continue;
                    }
                    RasterOverlayTile.MoreDetailAvailable moreDetailAvailable = mappedRasterTile.update(this);
                    moreRasterDetailAvailable |= (moreDetailAvailable == RasterOverlayTile.MoreDetailAvailable.Yes) ? true : false;
                }
                if (moreRasterDetailAvailable && !this.children.Any())
                {
                    createQuadtreeSubdividedChildren(this);
                }
            }
        }

        private void createQuadtreeSubdividedChildren(Tile parent)
        {
            // TODO: support non-BoundingRegions.
            BoundingRegion pRegion = parent.boundingVolume.pBoundingRegion;
            if (!pRegion)
            {
                BoundingRegionWithLooseFittingHeights pLooseRegion = parent.boundingVolume.pLooseRegion;
                if (pLooseRegion)
                {
                    pRegion = pLooseRegion.getBoundingRegion();
                }
            }
            if (!pRegion)
            {
                return;
            }

            // TODO: support upsampling non-quadtrees.
            QuadtreeTileID? pParentTileID = parent.ID.quadtreeID;
            if (pParentTileID.HasValue)
            {
                UpsampledQuadtreeNode? pUpsampledID = parent.ID.upsampledQuadtreeID;
                if (pUpsampledID.HasValue)
                {
                    pParentTileID = pUpsampledID.Value.tileID;
                }
            }

            if (!pParentTileID.HasValue)
            {
                return;
            }

            // TODO: support upsampling non-implicit tiles.
            if (parent.pContext.implicitContext == null)
            {
                return;
            }

            QuadtreeTileID swID = new QuadtreeTileID(pParentTileID.Value.level + 1, pParentTileID.Value.x * 2, pParentTileID.Value.y * 2);
            QuadtreeTileID seID = new QuadtreeTileID(swID.level, swID.x + 1, swID.y);
            QuadtreeTileID nwID = new QuadtreeTileID(swID.level, swID.x, swID.y + 1);
            QuadtreeTileID neID = new QuadtreeTileID(swID.level, swID.x + 1, swID.y + 1);

            QuadtreeTilingScheme tilingScheme = parent.pContext.implicitContext.tilingScheme;
            Projection projection = parent.pContext.implicitContext.projection;

            parent.createChildTiles(4);

            var children = parent.children;
            Tile sw = children[0];
            Tile se = children[1];
            Tile nw = children[2];
            Tile ne = children[3];

            sw.pContext = (parent.pContext);
            se.pContext = (parent.pContext);
            nw.pContext = (parent.pContext);
            ne.pContext = (parent.pContext);

            double geometricError = parent.geometricError * 0.5;
            sw.geometricError = (geometricError);
            se.geometricError = (geometricError);
            nw.geometricError = (geometricError);
            ne.geometricError = (geometricError);

            sw.pParent = parent;
            se.pParent = parent;
            nw.pParent = parent;
            ne.pParent = parent;

            sw.setTileID(new UpsampledQuadtreeNode(swID));
            se.setTileID(new UpsampledQuadtreeNode(seID));
            nw.setTileID(new UpsampledQuadtreeNode(nwID));
            ne.setTileID(new UpsampledQuadtreeNode(neID));

            double minimumHeight = pRegion.getMinimumHeight();
            double maximumHeight = pRegion.getMaximumHeight();

            sw.setBoundingVolume(new BoundingRegionWithLooseFittingHeights(new BoundingRegion(
               Projection.unprojectRectangleSimple(projection, tilingScheme.tileToRectangle(swID)),
                minimumHeight,
                maximumHeight)));
            se.setBoundingVolume(new BoundingRegionWithLooseFittingHeights(new BoundingRegion(
                Projection.unprojectRectangleSimple(projection, tilingScheme.tileToRectangle(seID)),
                minimumHeight,
                maximumHeight)));
            nw.setBoundingVolume(new BoundingRegionWithLooseFittingHeights(new BoundingRegion(
               Projection.unprojectRectangleSimple(projection, tilingScheme.tileToRectangle(nwID)),
                minimumHeight,
                maximumHeight)));
            ne.setBoundingVolume(new BoundingRegionWithLooseFittingHeights(new BoundingRegion(
               Projection.unprojectRectangleSimple(projection, tilingScheme.tileToRectangle(neID)),
                minimumHeight,
                maximumHeight)));
        }

        private void setBoundingVolume(BoundingRegionWithLooseFittingHeights boundingRegionWithLooseFittingHeights)
        {
            this.boundingVolume = new BoundingVolume(boundingRegionWithLooseFittingHeights);
        }

        private void setTileID<T>(T quadtreeNode) where T : struct
        {
            if (quadtreeNode is UpsampledQuadtreeNode upsampleNode)
            {
                ID.upsampledQuadtreeID = upsampleNode;
                ID.quadtreeID = null;
            }
            if (quadtreeNode is QuadtreeTileID node)
            {
                ID.quadtreeID = node;
                ID.upsampledQuadtreeID = null;
            }
        }

        private void createImplicitTile(ImplicitTilingContext implicitContext, Tile parent, Tile child, QuadtreeTileID childID, int available)
        {
            child.pContext = parent.pContext;
            child.pParent = parent;
            if (available > 0)
            {
                child.ID.quadtreeID = childID;
            }
            else
            {
                //   child.ID.quadtreeID=
            }
            child.geometricError = parent.geometricError * 0.5;

            double minimumHeight = -1000.0;
            double maximumHeight = 9000.0;

            BoundingRegion pRegion = parent.boundingVolume.pBoundingRegion;
            if (!pRegion)
            {
                var pLooseRegion = parent.boundingVolume.pLooseRegion;
                if (pLooseRegion != null)
                {
                    pRegion = pLooseRegion.getBoundingRegion();
                }
            }

            if (pRegion)
            {
                minimumHeight = pRegion.getMinimumHeight();
                maximumHeight = pRegion.getMaximumHeight();
            }

            var childRegion = new BoundingRegion(Projection.unprojectRectangleSimple(implicitContext.projection, implicitContext.tilingScheme.tileToRectangle(childID)));
            child.boundingVolume = new BoundingVolume(new BoundingRegionWithLooseFittingHeights(childRegion));
        }

        private void setState(LoadState done)
        {
            Interlocked.Exchange(ref this._state, (int)done);
        }

        private void setUnconditionallyRefine()
        {
            this.geometricError = double.PositiveInfinity;
        }

        public bool unloadContent()
        {
            if (this.state != Tile.LoadState.Unloaded)
            {
                // Cannot unload while an async operation is in progress.
                if (this.state == Tile.LoadState.ContentLoading)
                {
                    return false;
                }

                // If a child tile is being upsampled from this one, we can't unload this
                // one yet.
                if (this.state == Tile.LoadState.Done && this.children.Any())
                {
                    foreach (Tile child in this.children)
                    {
                        if (child.state == Tile.LoadState.ContentLoading && child.ID.upsampledQuadtreeID != null)
                        {
                            return false;
                        }
                    }
                }

                this.pTileset.notifyTileUnloading(this);
                TilesetExternals externals = this.pTileset.externals;
                if (externals.pPrepareRendererResources != null)
                {
                    if (this.state == LoadState.ContentLoaded)
                    {
                        externals.pPrepareRendererResources.free(this, this.pRendererResources, null);
                    }
                    else
                    {
                        externals.pPrepareRendererResources.free(this, null, this.pRendererResources);
                    }
                }

                this.setState(LoadState.Unloaded);
            }

            this.pRendererResources = null;
            // this.pContent.reset();
            this.pContent = null;

            this.rasterTiles.Clear();

            return true;
        }

        struct LoadResult
        {
            public LoadState state;
            public TileContentLoadResult pContent;
            public object pRendererResources;
        };
        public async UniTaskVoid loadContent()
        {
            if (this.state != LoadState.Unloaded)
            {
                foreach (RasterMappedTo3DTile mapped in this.rasterTiles)
                {
                    RasterOverlayTile pLoading = mapped.pLoadingTile;
                    if (pLoading && pLoading.State == RasterOverlayTile.LoadState.Unloaded)
                    {
                        RasterOverlayTileProvider pProvider = pLoading.pOverlay.getTileProvider();
                        if (pProvider)
                        {
                            pProvider.loadTileThrottled(pLoading);
                        }
                    }
                }
                return;
            }
            this.setState(LoadState.ContentLoading);
            List<Projection> projections = new List<Projection>();

            var requestUrl = pTileset.getResolvedContentUrl(this);
            //    this.notifyTileStartLoading(&tile);
            if (string.IsNullOrEmpty(requestUrl))
            {
                var pSubdivided = this.ID.upsampledQuadtreeID;
                if (pSubdivided.HasValue)
                {
                    // We can't upsample this tile until its parent tile is done loading.
                    if (this.pParent != null && this.pParent.state == LoadState.Done)
                    {
                        this.loadOverlays(projections);
                        this.upsampleParent(projections);
                    }
                    else
                    {
                        // Try again later. Push the parent tile loading along if we can.
                        if (this.pParent != null)
                        {
                            this.pParent.loadContent();
                        }
                        this.setState(LoadState.Unloaded);
                    }
                }
                else
                {
                    this.setState(LoadState.ContentLoaded);
                }

                return;
            }

            this.loadOverlays(projections);

            var request = http.requestAsset(requestUrl, null, this.pContext.requestHeaders);

            var input = new TileContentLoadInput(this);
            input.pRequest = request;

            Axis gltfUpAxis = pTileset.getGltfUpAxis();

            var generateMissingNormalsSmooth = pTileset.Options.contentOptions.generateMissingNormalsSmooth;
            var pPrepareRendererResources = pTileset.externals.pPrepareRendererResources;

            await request.SendWebRequest();
            pContent = await TileContentFactory.createContent(input);
            request.Dispose();

            object pRendererResources = null;
            if (pContent)
            {
                if (pContent.model)
                {
                    Gltf.Model model = pContent.model;
                    model.extras["gltfUpAxis"] = (int)gltfUpAxis;
                    generateTextureCoordinates(model, input.tileTransform, input.tileBoundingVolume, projections);
                    pContent.rasterOverlayProjections = projections;

                    if (generateMissingNormalsSmooth)
                    {
                        pContent.model.generateMissingNormalsSmooth();
                    }

                    if (pPrepareRendererResources != null)
                    {
                        Debug.Log("prepareInLoadThread");
                        await UniTask.SwitchToMainThread();
                        pRendererResources = pPrepareRendererResources.prepareInLoadThread(pContent.model, input.tileTransform);
                    }
                }
            }

            var loadResult = new LoadResult { state = LoadState.ContentLoaded, pContent = pContent, pRendererResources = pRendererResources };
            this.pContent = loadResult.pContent;
            this.pRendererResources = loadResult.pRendererResources;
            this.pTileset.notifyTileDoneLoading(this);
            this.setState(loadResult.state);

        }

        private async UniTask upsampleParent(List<Projection> projections)
        {
            Tile pParent = this.pParent;
            UpsampledQuadtreeNode? pSubdividedParentID = this.ID.upsampledQuadtreeID;
            Debug.Assert(pParent != null);
            Debug.Assert(pParent.state == LoadState.Done);
            Debug.Assert(pSubdividedParentID != null);

            TileContentLoadResult pParentContent = pParent.pContent;
            if (pParentContent == null || pParentContent.model == null)
            {
                this.setState(LoadState.ContentLoaded);
                return;
            }
            Gltf.Model parentModel = pParentContent.model;
            Tileset pTileset = this.pTileset;
            pTileset.notifyTileStartLoading(this);
            try
            {
                //var loadResult = await UniTask.RunOnThreadPool(() =>
                Func<LoadResult> load = (() =>
                {
                    TileContentLoadResult pContent = new TileContentLoadResult();
                    pContent.model = upsampleGltf.upsampleGltfForRasterOverlays(parentModel, pSubdividedParentID.Value);

                    if (pContent.model)
                    {
                        pContent.updatedBoundingVolume = generateTextureCoordinates(
                            pContent.model,
                            transform,
                            boundingVolume,
                            projections);
                        pContent.rasterOverlayProjections = projections;
                    }

                    var pPrepareRendererResources = pTileset.externals.pPrepareRendererResources;
                    object pRendererResources = null;
                    if (pContent.model && pPrepareRendererResources != null)
                    {
                        pRendererResources = pPrepareRendererResources.prepareInLoadThread(pContent.model, transform);
                    }
                    return new LoadResult()
                    {
                        state = LoadState.ContentLoaded,
                        pContent = pContent,
                        pRendererResources = pRendererResources
                    };
                });
                var loadResult = load();
                this.pContent = loadResult.pContent;
                this.pRendererResources = loadResult.pRendererResources;
                this.pTileset.notifyTileDoneLoading(this);
                this.setState(loadResult.state);
            }
            catch (System.Exception e)
            {
                Debug.LogException(e);
                this.pContent = null;
                this.pRendererResources = null;
                this.pTileset.notifyTileDoneLoading(this);
                this.setState(LoadState.Failed);
            }

            //   .thenInMainThread([this](LoadResult && loadResult) noexcept {
            //             this->_pContent = std::move(loadResult.pContent);
            //             this->_pRendererResources = loadResult.pRendererResources;
            //             this->getTileset()->notifyTileDoneLoading(this);
            //             this->setState(loadResult.state);
            //         })
            //   .catchInMainThread([this](const std::exception& /*e*/) noexcept {
            //             this->_pContent.reset();
            //             this->_pRendererResources = nullptr;
            //             this->getTileset()->notifyTileDoneLoading(this);
            //             this->setState(LoadState::Failed);
            //         });
        }


        private BoundingRegion generateTextureCoordinates(Model model, double4x4 tileTransform, BoundingVolume tileBoundingVolume, List<Projection> projections)
        {
            BoundingRegion result = null;

            // Generate texture coordinates for each projection.
            if (projections != null)
            {
                GlobeRectangle pRectangle = Impl.obtainGlobeRectangle(boundingVolume);
                if (pRectangle)
                {
                    for (int i = 0; i < projections.Count; ++i)
                    {
                        Projection projection = projections[i];

                        int textureCoordinateID = i;

                        Rectangle rectangle = Projection.projectRectangleSimple(projection, pRectangle);
                        BoundingRegion boundingRegion = GltfContent.createRasterOverlayTextureCoordinates(
                                        model,
                                        transform,
                                        textureCoordinateID,
                                        projection,
                                        rectangle);
                        if (result)
                        {
                            result = boundingRegion.computeUnion(result);
                        }
                        else
                        {
                            result = boundingRegion;
                        }
                    }
                }
            }

            return result;
        }

        private void loadOverlays(List<Projection> projections)
        {
            Debug.Assert(!this.rasterTiles.Any());
            Debug.Assert(this.state == LoadState.ContentLoading);
            if (pTileset.SupportsRasterOverlays)
            {

                RasterOverlayCollection overlays = pTileset.Overlays;
                mapRasterOverlaysToTile(this, overlays, projections);
            }
        }

        private void mapRasterOverlaysToTile(Tile tile, RasterOverlayCollection overlays, List<Projection> projections)
        {
            Debug.Assert(!tile.rasterTiles.Any());

            GlobeRectangle pRectangle = Impl.obtainGlobeRectangle(tile.boundingVolume);
            if (!pRectangle)
            {
                return;
            }

            // Find the unique projections; we'll create texture coordinates for each
            // later.
            foreach (RasterOverlay pOverlay in overlays)
            {
                RasterOverlayTileProvider pProvider = pOverlay.getTileProvider();

                // Project the globe rectangle to the raster's coordinate system.
                // TODO: we can compute a much more precise projected rectangle by
                //       projecting each vertex, but that would require us to have
                //       geometry first.
                Rectangle overlayRectangle = Projection.projectRectangleSimple(pProvider.pProjection, pRectangle);

                var pRaster = pOverlay.getTileProvider().getTile(overlayRectangle, tile.geometricError);
                if (pRaster)
                {
                    RasterMappedTo3DTile mappedTile = new RasterMappedTo3DTile(pRaster);
                    tile.rasterTiles.Add(mappedTile);
                    var provider = pRaster.pOverlay.getTileProvider();
                    Projection projection = provider.pProjection;

                    int projectionID = getTextureCoordinatesForProjection(projections, projection);

                    mappedTile.setTextureCoordinateID(projectionID);

                    if (pRaster.State != RasterOverlayTile.LoadState.Placeholder)
                    {
                        pOverlay.getTileProvider().loadTileThrottled(pRaster);
                    }
                }
            }

            // Add geographic texture coordinates for water mask.
            if (tile.pTileset.Options.contentOptions.enableWaterMask)
            {
                getTextureCoordinatesForProjection(projections, new Projection(new GeographicProjection()));
            }
        }

        private int getTextureCoordinatesForProjection(List<Projection> projections, Projection projection)
        {
            var existingCoordinatesIt = projections.FindIndex(x => x.Equals(projection));
            if (existingCoordinatesIt < 0)
            {
                // New set of texture coordinates.
                projections.Add(projection);
                return projections.Count - 1;
            }
            else
            {
                return existingCoordinatesIt;
            }
        }

        public bool isRenderable()
        {
            if (state >= LoadState.ContentLoaded)
            {
                if (this.pContent && this.pContent.model)
                {
                    return rasterTiles.All(x => x.pReadyTile != null);
                }
            }
            return false;
        }

        internal bool getUnconditionallyRefine()
        {
            return math.isinf(this.geometricError);
        }

        internal bool isExternalTileset()
        {
            return this.state >= LoadState.ContentLoaded && this.pContent && !this.pContent.model;
        }


        // public class pPrepareRendererResources
        // {
        //     internal static object prepareInLoadThread(Model model, double4x4 tileTransform)
        //     {
        //         throw new NotImplementedException();
        //     }

        //     internal static unsafe object prepareRasterInLoadThread(ImageCesium image)
        //     {
        //         throw new NotImplementedException();
        //     }
        // }
    }
}